#include "GLES2RenderSystem.h"
#include "GLES2TextureManager.h"
#include "GLES2DepthBuffer.h"
#include "GLES2HardwarePixelBuffer.h"
#include "GLES2HardwareBufferManager.h"
#include "GLES2HardwareIndexBuffer.h"
#include "GLES2HardwareVertexBuffer.h"
#include "GLES2GpuProgramManager.h"
#include "GLES2Util.h"
#include "GLES2FBORenderTexture.h"
#include "GLSLESProgramFactory.h"
#include "GLSLESLinkProgram.h"
#include "GLSLESLinkProgramManager.h"

#define VBO_BUFFER_OFFSET(i) ((char*)NULL + (i))
#define GL_FILL 0x1B02

namespace PVM
{
	GLES2RenderSystem::GLES2RenderSystem()
	: mDepthWrite(true),
	  mStencilMask(0xFFFFFFFF),
	  mGpuProgramManager(0),
	  mGLSLESProgramFactory(0),
	  mHardwareBufferManager(0),
	  mRTTManager(0)
	{
		size_t i;
		
		LogManager::getSingleton().logMessage(getName() + " created.");
		
		mEnabledFixedPipeline = false;
		
		mGLSupport = getGLSupport();
		mWorldMatrix = Matrix4::IDENTITY;
		mViewMatrix = Matrix4::IDENTITY;
		mGLSupport->addConfig();
		mColourWrite[0] = mColourWrite[1] = mColourWrite[2] = mColourWrite[3] = true;
		
		for (i = 0; i < MAX_TEXTURE_LAYERS; i++)
		{
			mTextureCoordIndex[i] = 99;
			mTextureTypes[i] = 0;
		}
		
		mActiveRenderTarget = 0;
		mCurrentContext = 0;
		mMainContext = 0;
		mGLInitialised = false;
		mTextureMipmapCount = 0;
		mMinFilter = FO_LINEAR;
		mMipFilter = FO_POINT;
		mCurrentVertexProgram = 0;
		mCurrentFragmentProgram = 0;
		mPolygonMode = GL_FILL;
	}
	
	GLES2RenderSystem::~GLES2RenderSystem()
	{
		shutdown();
		
		RenderTargetMap::iterator i;
		for (i = mRenderTargets.begin(); i != mRenderTargets.end(); ++i)
		{
			delete i->second;
		}
		
		mRenderTargets.clear();
		delete mGLSupport;
	}
	
	const std::string& GLES2RenderSystem::getName(void) const
	{
		static std::string strName("OpenGL ES 2.x Rendering Subsystem");
		return strName;
	}
	
	ConfigOptionMap& GLES2RenderSystem::getConfigOptions(void)
	{
		return mGLSupport->getConfigOptions();
	}
	
	void GLES2RenderSystem::setConfigOption(const std::string& name, const std::string& value)
	{
		mGLSupport->setConfigOption(name, value);
	}
	
	std::string GLES2RenderSystem::validateConfigOptions(void)
	{
		return mGLSupport->validateConfig();
	}
	
	void GLES2RenderSystem::_initialise()
	{
		mGLSupport->start();
		
		RenderSystem::_initialise();
		return;
	}
	
	RenderSystemCapabilities* GLES2RenderSystem::createRenderSystemCapabilities() const
	{
		RenderSystemCapabilities* rsc = new RenderSystemCapabilities();
		
		rsc->setCategoryRelevant(CAPS_CATEGORY_GL, true);
		rsc->setDriverVersion(mDriverVersion);
		
		const char* deviceName = (const char*)glGetString(GL_RENDERER);
		const char* vendorName = (const char*)glGetString(GL_VENDOR);
		if (deviceName)
		{
			rsc->setDeviceName(deviceName);
		}
		
		rsc->setRenderSystemName(getName());
		
		if (strstr(vendorName, "Imagination Technologies"))
			rsc->setVendor(GPU_IMAGINATION_TECHNOLOGIES);
		else if (strstr(vendorName, "Apple Computer, Inc."))
			rsc->setVendor(GPU_APPLE);
		else if (strstr(vendorName, "NVIDIA"))
			rsc->setVendor(GPU_NVIDIA);
		else
			rsc->setVendor(GPU_UNKNOWN);
		
		GLint units;
		glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &units);
		rsc->setNumTextureUnits(units);
		
		GLint stencil;
		glGetIntegerv(GL_STENCIL_BITS, &stencil);
		if (stencil)
		{
			rsc->setCapability(RSC_HWSTENCIL);
			rsc->setCapability(RSC_TWO_SIDED_STENCIL);
			rsc->setStencilBufferBitDepth(stencil);
		}
		
		rsc->setCapability(RSC_SCISSOR_TEST);
		rsc->setCapability(RSC_VBO);
		
		if (mGLSupport->checkExtension("GL_IMG_texture_compression_pvrtc") ||
			mGLSupport->checkExtension("GL_EXT_texture_compression_dxt1") ||
			mGLSupport->checkExtension("GL_EXT_texture_compression_s3tc"))
		{
			rsc->setCapability(RSC_TEXTURE_COMPRESSION);
			
			if (mGLSupport->checkExtension("GL_IMG_texture_compression_pvrtc"))
				rsc->setCapability(RSC_TEXTURE_COMPRESSION_PVRTC);
			if (mGLSupport->checkExtension("GL_EXT_texture_compression_dxt1") &&
				mGLSupport->checkExtension("GL_EXT_texture_compression_s3tc"))
				rsc->setCapability(RSC_TEXTURE_COMPRESSION_DXT);
		}
		
		if (mGLSupport->checkExtension("GL_EXT_texture_filter_anisotropic"))
			rsc->setCapability(RSC_ANISOTROPY);
		
		rsc->setCapability(RSC_FBO);
		rsc->setCapability(RSC_HWRENDER_TO_TEXTURE);
		rsc->setNumMultiRenderTargets(1);
		
		rsc->setCapability(RSC_CUBEMAPPING);
		rsc->setCapability(RSC_STENCIL_WRAP);
		rsc->setVertexTextureUnitsShared(true);
		rsc->setCapability(RSC_AUTOMIPMAP);
		rsc->setCapability(RSC_BLENDING);
		rsc->setCapability(RSC_ADVANCED_BLEND_OPERATIONS);
		
		rsc->setCapability(RSC_DOT3);
		GLfloat psRange[2] = {0.0, 0.0};
		glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, psRange);
		rsc->setMaxPointSize(psRange[1]);
		
		rsc->setCapability(RSC_POINT_SPRITES);
		rsc->setCapability(RSC_POINT_EXTENDED_PARAMETERS);
		
		rsc->addShaderProfile("glsles");
		LogManager::getSingleton().logMessage("GLSL ES support detected");
		
		rsc->setCapability(RSC_VERTEX_FORMAT_UBYTE4);
		rsc->setCapability(RSC_INFINITE_FAR_PLANE);
		
		rsc->setCapability(RSC_VERTEX_PROGRAM);
		rsc->setCapability(RSC_FRAGMENT_PROGRAM);
		
		GLfloat floatConstantCount = 0;
		glGetFloatv(GL_MAX_VERTEX_UNIFORM_VECTORS, &floatConstantCount);
		rsc->setVertexProgramConstantFloatCount((unsigned short)floatConstantCount);
		rsc->setVertexProgramConstantBoolCount((unsigned short)floatConstantCount);
		rsc->setVertexProgramConstantIntCount((unsigned short)floatConstantCount);
		
		floatConstantCount = 0;
		glGetFloatv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &floatConstantCount);
		rsc->setFragmentProgramConstantFloatCount((unsigned short)floatConstantCount);
		rsc->setFragmentProgramConstantBoolCount((unsigned short)floatConstantCount);
		rsc->setFragmentProgramConstantIntCount((unsigned short)floatConstantCount);
		
		rsc->setGeometryProgramConstantFloatCount(0);
		rsc->setGeometryProgramConstantBoolCount(0);
		rsc->setGeometryProgramConstantIntCount(0);
		
		rsc->setCapability(RSC_ALPHA_TO_COVERAGE);
		rsc->setMaxPointSize(0.f);
		
		if (mGLSupport->checkExtension("GL_OES_get_program_binary"))
		{
			rsc->setCapability(RSC_CAN_GET_COMPILED_SHARED_BUFFER);
		}
		
		return rsc;
	}
	
	void GLES2RenderSystem::initialiseFromRenderSystemCapabilities(RenderSystemCapabilities* caps, RenderTarget* primary)
	{
		if (caps->getRenderSystemName() != getName())
		{
			PVM_EXCEPT("Trying to initialize GLES2RenderSystem from RenderSystemCapabilities that do not support OpenGL ES",
					   "GLES2RenderSystem::initialiseFromRenderSystemCapabilities");
		}
		
		mGpuProgramManager = new GLES2GpuProgramManager();
		
		mGLSLESProgramFactory = new GLSLESProgramFactory();
		HighLevelGpuProgramManager::getSingleton().addFactory(mGLSLESProgramFactory);
		mFixedFunctionTextureUnits = caps->getNumTextureUnits();
		mHardwareBufferManager = new GLES2HardwareBufferManager();
		
		ConfigOptionMap::iterator cfi = getConfigOptions().find("RTT Preferred Mode");
		
		int rttMode = 0;
		if (cfi != getConfigOptions().end())
		{
			if (cfi->second.currentValue == "PBuffer")
			{
				rttMode = 1;
			}
			else if (cfi->second.currentValue == "Copy")
			{
				rttMode = 2;
			}
		}
		
		if (caps->hasCapability(RSC_FBO) && rttMode < 1)
		{
			LogManager::getSingleton().logMessage("GL ES 2: Using FBOs for rendering to textures");
			mRTTManager = new GLES2FBOManager();
			caps->setCapability(RSC_RTT_SEPARATE_DEPTHBUFFER);
		}
		else
		{
			if (caps->hasCapability(RSC_PBUFFER) && rttMode < 2)
			{
				if (caps->hasCapability(RSC_HWRENDER_TO_TEXTURE))
				{
					LogManager::getSingleton().logMessage("GL ES 2: Using PBuffers for rendering to textures");
				}
			}
			else
			{
				mRTTManager = new GLES2CopyingRTTManager();
				LogManager::getSingleton().logMessage("GL ES 2: Using framebuffer copy for renderint to textures (worst)");
				LogManager::getSingleton().logMessage("GL ES 2: Warning: RenderTexture size is restricted to size of framebuffer. If you are on Linux, consider using GLX instead of SDL.");
				
				caps->setCapability(RSC_RTT_SEPARATE_DEPTHBUFFER);
				caps->setCapability(RSC_RTT_DEPTHBUFFER_RESOLUTION_LESSEQUAL);
			}
			
			caps->setNumMultiRenderTargets(1);
		}
		
		Log* defaultLog = LogManager::getSingleton().getDefaultLog();
		if (defaultLog)
		{
			caps->log(defaultLog);
		}
		
		mTextureManager = new GLES2TextureManager(*mGLSupport);
		mGLInitialised = true;
	}
	
	void GLES2RenderSystem::reinitialise(void)
	{
		this->shutdown();
		this->_initialise(true);
	}
	
	void GLES2RenderSystem::shutdown(void)
	{
		if (mGLSLESProgramFactory)
		{
			if (HighLevelGpuProgramManager::getSingletonPtr())
				HighLevelGpuProgramManager::getSingleton().removeFactory(mGLSLESProgramFactory);
			delete mGLSLESProgramFactory;
			mGLSLESProgramFactory = 0;
		}
		
		delete mGpuProgramManager;
		mGpuProgramManager = 0;
		
		delete mHardwareBufferManager;
		mHardwareBufferManager = 0;
		
		delete mRTTManager;
		mRTTManager = 0;
		
		delete mTextureManager;
		mTextureManager = 0;
		
		RenderSystem::shutdown();
		mGLSupport->stop();
		mGLInitialised = 0;
	}
	
	DepthBuffer* GLES2RenderSystem::_createDepthBufferFor(RenderTarget* renderTarget)
	{
		GLES2DepthBuffer* retVal = 0;
		GLES2FrameBufferObject* fbo = 0;
		renderTarget->getCustomAttribute("FBO", &fbo);
		
		if (fbo)
		{
			GLuint depthFormat, stencilFormat;
			static_cast<GLES2FBOManager*>(mRTTManager)->getBestDepthStencil(fbo->getFormat(),
																			&depthFormat, &stencilFormat);
			GLES2RenderBuffer* depthBuffer = new GLES2RenderBuffer(depthBuffer, fbo->getWidth(),
																   fbo->getHeight(), fbo->getFSAA());
			GLES2RenderBuffer* stencilBuffer = depthBuffer;
			if (depthFormat != GL_DEPTH24_STENCIL8_OES &&
				stencilBuffer != GL_NONE)
			{
				stencilBuffer = new GLES2RenderBuffer(stencilFormat, fbo->getWidth(),
													  fbo->getHeight(), fbo->getFSAA());
			}
			
			retVal = new GLES2DepthBuffer(0, this, mCurrentContext, depthBuffer, stencilBuffer,
										  fbo->getWidth(), fbo->getHeight(), fbo->getFSAA(), 0, false);
		}
		
		return retVal;
	}
	
	void GLES2RenderSystem::_getDepthStencilFormatFor(GLenum internalColourFormat, GLenum* depthFormat,
													  GLenum* stencilFormat)
	{
		mRTTManager->getBestDepthStencil(internalColourFormat, depthFormat, stencilFormat);
	}
	
	MultiRenderTarget* GLES2RenderSystem::createMultiRenderTarget(const std::string& name)
	{
		MultiRenderTarget* retVal = mRTTManager->createMultiRenderTarget(name);
		attachRenderTarget(*retVal);
		return retVal;
	}
	
	std::string GLES2RenderSystem::getErrorDescription(long errorNumber) const
	{
		return StringUtil::BLANK;
	}
	
	VertexElementType GLES2RenderSystem::getColourVertexElementType(void) const
	{
		return VET_COLOUR_ABGR;
	}
	
	void GLES2RenderSystem::_setWorldMatrix(const Matrix4& m)
	{
		mWorldMatrix = m;
	}
	
	void GLES2RenderSystem::_setViewMatrix(const Matrix4& m)
	{
		mViewMatrix = m;
		
		if (!mClipPlanes.empty())
		{
			mClipPlanesDirty = true;
		}
	}
	
	void GLES2RenderSystem::_setProjectionMatrix(const Matrix4& m)
	{
		if (!mClipPlanes.empty())
			mClipPlanesDirty = true;
	}
	
	void GLES2RenderSystem::_setTexture(size_t stage, bool enabled, const TexturePtr& texPtr)
	{
		GLES2TexturePtr tex = texPtr;
		
		if (!activateGLTextureUnit(stage))
			return;
		
		if (enabled)
		{
			if (!tex.isNull())
			{
				tex->touch();
				mTextureTypes[stage] = tex->getGLES2TextureTarget();
				
				mTextureMipmapCount = tex->getNumMipmaps();
			}
			else
				mTextureTypes[stage] = GL_TEXTURE_2D;
			
			if (!tex.isNull())
				glBindTexture(mTextureTypes[stage], tex->getGLID());
			else
				glBindTexture(mTextureTypes[stage], static_cast<GLES2TextureManager*>(mTextureManager)->getWarningTextureID());
		}
		else
		{
			glBindTexture(GL_TEXTURE_2D, 0);
		}
		
		activateGLTextureUnit(0);
	}
	
	void GLES2RenderSystem::_setTextureCoordSet(size_t stage, size_t index)
	{
		mTextureCoordIndex[stage] = index;
	}
	
	GLint GLES2RenderSystem::getTextureAddressingMode(TextureUnitState::TextureAddressingMode tam) const
	{
		switch(tam)
		{
			case TextureUnitState::TAM_CLAMP:
			case TextureUnitState::TAM_BORDER:
				return GL_CLAMP_TO_EDGE;
			case TextureUnitState::TAM_MIRROR:
				return GL_MIRRORED_REPEAT;
			case TextureUnitState::TAM_WRAP:
			default:
				return GL_REPEAT;
		}
	}
	
	void GLES2RenderSystem::_setTextureAddressingMode(size_t stage, const TextureUnitState::UVWAddressingMode& uvw)
	{
		if (!activateGLTextureUnit(stage))
			return;
		glTexParameteri(mTextureTypes[stage], GL_TEXTURE_WRAP_S, getTextureAddressingMode(uvw.u));
		glTexParameteri(mTextureTypes[stage], GL_TEXTURE_WRAP_T, getTextureAddressingMode(uvw.v));
		activateGLTextureUnit(0);
	}
	
	GLenum GLES2RenderSystem::getBlendMode(SceneBlendFactor Blend) const
	{
		switch(Blend)
		{
			case SBF_ONE:
				return GL_ONE;
			case SBF_ZERO:
				return GL_ZERO;
			case SBF_DEST_COLOUR:
				return GL_DST_COLOR;
			case SBF_SOURCE_COLOUR:
				return GL_SRC_COLOR;
			case SBF_ONE_MINUS_DEST_COLOUR:
				return GL_ONE_MINUS_DST_COLOR;
			case SBF_ONE_MINUS_SOURCE_COLOUR:
				return GL_ONE_MINUS_SRC_COLOR;
			case SBF_DEST_ALPHA:
				return GL_DST_ALPHA;
			case SBF_SOURCE_ALPHA:
				return GL_SRC_ALPHA;
			case SBF_ONE_MINUS_DEST_ALPHA:
				return GL_ONE_MINUS_DST_ALPHA;
			case SBF_ONE_MINUS_SOURCE_ALPHA:
				return GL_ONE_MINUS_SRC_ALPHA;
		};
		
		return GL_ONE;
	}
	
	void GLES2RenderSystem::_setSceneBlending(SceneBlendFactor sourceFactor, SceneBlendFactor destFactor, SceneBlendOperation op)
	{
		GLenum sourceBlend = getBlendMode(sourceFactor);
		GLenum destBlend = getBlendMode(destFactor);
		if (sourceFactor == SBF_ONE && destFactor == SBF_ZERO)
		{
			glDisable(GL_BLEND);
		}
		else
		{
			glEnable(GL_BLEND);
			glBlendFunc(sourceBlend, destBlend);
		}
		
		GLint func = GL_FUNC_ADD;
		switch(op)
		{
			case SBO_ADD:
				func = GL_FUNC_ADD;
				break;
			case SBO_SUBTRACT:
				func = GL_FUNC_SUBTRACT;
				break;
			case SBO_REVERSE_SUBTRACT:
				func = GL_FUNC_REVERSE_SUBTRACT;
				break;
			case SBO_MIN:
				func = GL_MIN_EXT;
				break;
			case SBO_MAX:
				func = GL_MAX_EXT;
				break;
		}
		
		glBlendEquation(func);
	}
	
	void GLES2RenderSystem::_setSeparateSceneBlending(
	SceneBlendFactor sourceFactor, SceneBlendFactor destFactor,
	SceneBlendFactor sourceFactorAlpha, SceneBlendFactor destFactorAlpha,
	SceneBlendOperation op, SceneBlendOperation alphaOp)
	{
		GLenum sourceBlend = getBlendMode(sourceFactor);
		GLenum destBlend = getBlendMode(destFactor);
		GLenum sourceBlendAlpha = getBlendMode(sourceFactorAlpha);
		GLenum destBlendAlpha = getBlendMode(destFactorAlpha);
		
		if (sourceFactor == SBF_ONE && destFactor == SBF_ZERO &&
			sourceFactorAlpha == SBF_ONE && destFactorAlpha == SBF_ZERO)
		{
			glDisable(GL_BLEND);
		}
		else 
		{
			glEnable(GL_BLEND);
			glBlendFuncSeparate(sourceBlend, destBlend, sourceBlendAlpha, destBlendAlpha);
		}
		
		GLint func = GL_FUNC_ADD, alphaFunc = GL_FUNC_ADD;
		switch(op)
		{
			case SBO_ADD:
				func = GL_FUNC_ADD;
				break;
			case SBO_SUBTRACT:
				func = GL_FUNC_SUBTRACT;
				break;
			case SBO_REVERSE_SUBTRACT:
				func = GL_FUNC_REVERSE_SUBTRACT;
				break;
			case SBO_MIN:
				func = GL_MIN_EXT;
				break;
			case SBO_MAX:
				func = GL_MAX_EXT;
				break;
		}
		
		switch (alphaOp)
		{
			case SBO_ADD:
				alphaFunc = GL_FUNC_ADD;
				break;
			case SBO_SUBTRACT:
				alphaFunc = GL_FUNC_SUBTRACT;
				break;
			case SBO_REVERSE_SUBTRACT:
				alphaFunc = GL_FUNC_REVERSE_SUBTRACT;
				break;
			case SBO_MIN:
				alphaFunc = GL_MIN_EXT;
				break;
			case SBO_MAX:
				alphaFunc = GL_MAX_EXT;
				break;
		}
		
		glBlendFuncSeparate(func, alphaFunc);
	}
	
	void GLES2RenderSystem::_setAlphaRejectSettings(CompareFunction func, unsigned char value, bool alphaToCoverage)
	{
		bool a2c = false;
		static bool lasta2c = false;
		
		if (func != CMPF_ALWAYS_PASS)
		{
			a2c = alphaToCoverage;
		}
		
		if (a2c != lasta2c && getCapabilities()->hasCapability(RSC_ALPHA_TO_COVERAGE))
		{
			if (a2c)
				glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
			else
				glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
			
			lasta2c = a2c;
		}
	}
	
	void GLES2RenderSystem::_setViewport(Viewport* vp)
	{
		if (!vp)
		{
			mActiveViewport = NULL;
			_setRenderTarget(NULL);
		}
		else if (vp != mActiveViewport || vp->_isUpdated())
		{
			RenderTarget* target;
			
			target = vp->getTarget();
			_setRenderTarget(target);
			mActiveViewport = vp;
			
			GLsizei x, y, w, h;
			
			w = vp->getActualWidth();
			h = vp->getActualHeight();
			x = vp->getActualLeft();
			y = vp->getActualTop();
			
			if (!target->requiresTextureFlipping())
			{
				y = target->getHeight() - h - y;
			}
			
			ConfigOptionMap::const_iterator opt;
			ConfigOptionMap::const_iterator end = mGLSupport->getConfigOptions().end();
			
			if ((opt = mGLSupport->getConfigOptions().find("Orientation")) != end)
			{
				std::string val = opt->second.currentValue;
				std::string::size_type pos = val.find("Landscape");
				
				if (pos != std::string::npos)
				{
					GLsizei temp = h;
					h = w;
					w = temp;
				}
			}
			
			glViewport(x, y, w, h);
			glScissor(x, y, w, h);
			
			vp->_clearUpdatedFlag();
		}
	}
	
	void GLES2RenderSystem::_beginFrame(void)
	{
		if (!mActiveViewport)
			PVM_EXCEPT("Cannot begin frame - no viewport selected.",
					   "GLES2RenderSystem::_beginFrame");
		
		glEnable(GL_SCISSOR_TEST);
	}
	
	void GLES2RenderSystem::_endFrame(void)
	{
		glDisable(GL_SCISSOR_TEST);
		unbindGpuProgram(GPT_VERTEX_PROGRAM);
		unbindGpuProgram(GPT_FRAGMENT_PROGRAM);
	}
	
	void GLES2RenderSystem::_setCullingMode(CullingMode mode)
	{
		mCullingMode = mode;
		GLenum cullMode;
		switch(mode)
		{
			case CULL_NONE:
				glDisable(GL_CULL_FACE);
				return;
				
			default:
			case CULL_CLOCKWISE:
				if (mActiveRenderTarget &&
					((mActiveRenderTarget->requiresTextureFlipping() && !mInvertVertexWinding) ||
					 (!mActiveRenderTarget->requiresTextureFlipping() && mInvertVertexWinding)))
				{
					cullMode = GL_FRONT;
				}
				else
				{
					cullMode = GL_BACK;
				}
				break;
			case CULL_ANTICLOCKWISE:
				if (mActiveRenderTarget &&
					((mActiveRenderTarget->requiresTextureFlipping() && !mInvertVertexWinding) ||
					 (!mActiveRenderTarget->requiresTextureFlipping() && mInvertVertexWinding)))
				{
					cullMode = GL_BACK;
				}
				else
				{
					cullMode = GL_FRONT;
				}
				break;
		}
		
		glEnable(GL_CULL_FACE);
		glCullFace(cullMode);
	}
	
	void GLES2RenderSystem::_setDepthBufferParams(bool depthTest, bool depthWrite, CompareFunction depthFunction)
	{
		_setDepthBufferCheckEnabled(depthTest);
		_setDepthBufferWriteEnabled(depthWrite);
		_setDepthBufferFunction(depthFunction);
	}
	
	void GLES2RenderSystem::_setDepthBufferCheckEnabled(bool enabled)
	{
		if (enabled)
		{
			glClearDepthf(1.0f);
			glEnable(GL_DEPTH_TEST);
		}
		else
		{
			glDisable(GL_DEPTH_TEST);
		}
	}
	
	void GLES2RenderSystem::_setDepthBufferWriteEnabled(bool enabled)
	{
		GLboolean flag = enabled ? GL_TRUE : GL_FALSE;
		glDepthMask(flag);
		
		mDepthWrite = enabled;
	}
	
	void GLES2RenderSystem::_setDepthBufferFunction(CompareFunction func)
	{
		glDepthFunc(convertCompareFunction(func));
	}
	
	void GLES2RenderSystem::_setDepthBias(float constantBias, float slopeScaleBias)
	{
		if (constantBias != 0 || slopeScaleBias != 0)
		{
			glEnable(GL_POLYGON_OFFSET_FILL);
			glPolygonOffset(-slopeScaleBias, -constantBias);
		}
		else
		{
			glDisable(GL_POLYGON_OFFSET_FILL);
		}
	}
	
	void GLES2RenderSystem::_setColourBufferWriteEnabled(bool red, bool green, bool blue, bool alpha)
	{
		glColorMask(red, green, blue, alpha);
		
		mColourWrite[0] = red;
		mColourWrite[1] = blue;
		mColourWrite[2] = green;
		mColourWrite[3] = alpha;
	}
	
	void GLES2RenderSystem::_setFog(FogMode mode, const ColourValue& colour, Real density, Real start, Real end)
	{
	}
	
	void GLES2RenderSystem::_convertProjectionMatrix(const Matrix4& matrix,
													 Matrix4& dest,
													 bool forGpuProgram)
	{
		dest = matrix;
	}
	
	void GLES2RenderSystem::_makeProjectionMatrix(const Radian& fovy, Real aspect,
												  Real nearPlane, Real farPlane,
												  Matrix4& dest, bool forGpuProgram)
	{
		Radian thetaY(fovy / 2.0f);
		Real tanThetaY = Math::Tan(thetaY);
		
		Real w = (1.0f / tanThetaY) / aspect;
		Real h = 1.0f / tanThetaY;
		Real q, qn;
		if (farPlane == 0)
		{
			q = Frustum::INFINITE_FAR_PLANE_ADJUST - 1;
			qn = nearPlane * (Frustum::INFINITE_FAR_PLANE_ADJUST - 2);
		}
		else
		{
			q = -(farPlane + nearPlane) / (farPlane - nearPlane);
			qn = -2 * (farPlane * nearPlane) / (farPlane - nearPlane);
		}
		
		dest = Matrix4::ZERO;
		dest[0][0] = w;
		dest[1][1] = h;
		dest[2][2] = q;
		dest[3][3] = qn;
		dest[3][2] = -1;
	}
	
	void GLES2RenderSystem::_makeProjectionMatrix(Real left, Real right,
												  Real bottom, Real top,
												  Real nearPlane, Real farPlane,
												  Matrix4& dest, bool forGpuProgram)
	{
		Real width = right - left;
		Real height = top - bottom;
		Real q, qn;
		if (farPlane == 0)
		{
			q = Frustum::INFINITE_FAR_PLANE_ADJUST - 1;
			qn = nearPlane * (Frustum::INFINITE_FAR_PLANE_ADJUST - 2);
		}
		else
		{
			q = -(farPlane + nearPlane) / (farPlane - nearPlane);
			qn = -2 * (farPlane * nearPlane) / (farPlane - nearPlane);
		}
		
		dest = Matrix4::ZERO;
		dest[0][0] = 2 * nearPlane / width;
		dest[0][2] = (right + left) / width;
		dest[1][1] = 2 * nearPlane / height;
		dest[1][2] = (top + bottom) / height;
		dest[2][2] = q;
		dest[2][3] = qn;
		dest[3][2] = -1;
	}
	
	void GLES2RenderSystem::_makeOrithoMatrix(const Radian& fovy, Real aspect,
											  Real nearPlane, Real farPlane,
											  Matrix4& dest, bool forGpuProgram)
	{
		Radian thetaY(fovy / 2.0f);
		Real tanThetaY = Math::Tan(thetaY);
		
		Real tanThetaX = tanThetaY * aspect;
		Real half_w = tanThetaX * nearPlane;
		Real half_h = tanThetaY * nearPlane;
		Real iw = 1.0 / half_w;
		Real ih = 1.0 / half_h;
		Real q;
		if (farPlane == 0)
		{
			q = 0;
		}
		else
		{
			q = 2.0 / (farPlane - nearPlane);
		}
		dest = Matrix4::ZERO;
		dest[0][0] = iw;
		dest[1][1] = ih;
		dest[2][2] = -q;
		dest[2][3] = -(farPlane + nearPlane) / (farPlane - nearPlane);
		dest[3][3] = 1;
	}
	
	void GLES2RenderSystem::_applyObliqueDepthProjection(Matrix4& matrix,
														 const Plane& plane,
														 bool forGpuProgram)
	{
		Vector4 q;
		q.x = (Math::Sign(plane.normal.x) + matrix[0][2]) / matrix[0][0];
		q.y = (Math::Sign(plane.normal.y) + matrix[1][2]) / matrix[1][1];
		q.z = -1.0F;
		q.w = (1.0F + matrix[2][2]) / matrix[2][3];
		
		Vector4 clipPlane4d(plane.normal.x, plane.normal.y, plane.normal.z, plane.d);
		Vector4 c = clipPlane4d * (2.0F / (clipPlane4d.dotProduct(q)));
		
		matrix[2][0] = c.x;
		matrix[2][1] = c.y;
		matrix[2][2] = c.z + 1.0F;
		matrix[2][3] = c.w;
	}
	
	void GLES2RenderSystem::_setPolygonMode(PolygonMode level)
	{
		switch (level)
		{
			case PM_POINTS:
				mPolygonMode = GL_POINTS;
				break;
			case PM_WIREFRAME:
				mPolygonMode = GL_LINE_STRIP;
				break;
			default:
			case PM_SOLID:
				mPolygonMode = GL_FILL;
				break;
		}
	}
	
	void GLES2RenderSystem::setStencilCheckEnabled(bool enabled)
	{
		if (enabled)
		{
			glEnable(GL_STENCIL_TEST);
		}
		else
		{
			glDisable(GL_STENCIL_TEST);
		}
	}
	
	void GLES2RenderSystem::setStencilBufferParams(CompareFunction func,
												   uint32 refValue, uint32 mask,
												   StencilOperation stencilFailOp,
												   StencilOperation depthFailOp,
												   StencilOperation passOp,
												   bool twoSidedOperation)
	{
		bool flip;
		mStencilMask = mask;
		
		if (twoSidedOperation)
		{
			if (!mCurrentCapabilities->hasCapability(RSC_TWO_SIDED_STENCIL))
				PVM_EXCEPT("2-sided stencils are not supported",
						   "GLES2RenderSystem::setStencilBufferParams");
			
			flip = (mInvertVertexWinding && !mActiveRenderTarget->requiresTextureFlipping()) ||
			(!mInvertVertexWinding && mActiveRenderTarget->requiresTextureFlipping());
			
			glStencilMaskSeparate(GL_BACK, mask);
			glStencilFuncSeparate(GL_BACK, convertCompareFunction(func), refValue, mask);
			glStencilOpSeparate(GL_BACK,
								convertStencilOp(stencilFailOp, !flip),
								convertStencilOp(depthFailOp, !flip),
								convertStencilOp(passOp, !flip));
			
			glStencilMaskSeparate(GL_FRONT, mask);
			glStencilFuncSeparate(GL_FRONT, convertCompareFunction(func), refValue, mask);
			glStencilOpSeparate(GL_FRONT,
								convertStencilOp(stencilFailOp, flip),
								convertStencilOp(depthFailOp, flip),
								convertStencilOp(passOp, flip));
		}
		else
		{
			flip = false;
			glStencilMask(mask);
			glStencilFunc(convertCompareFunction(func), refValue, mask);
			glStencilOp(convertStencilOp(stencilFailOp, flip),
						convertStencilOp(depthFailOp, flip),
						convertStencilOp(passOp, flip));
		}
	}
	
	GLint GLES2RenderSystem::getCombinedMinMipFilter(void) const
	{
		switch (mMinFilter)
		{
			case FO_ANISOTROPIC:
			case FO_LINEAR:
				switch (mMipFilter)
			{
				case FO_ANISOTROPIC:
				case FO_LINEAR:
					return GL_LINEAR_MIPMAP_LINEAR;
				case FO_POINT:
				case FO_NONE:
					return GL_LINEAR;
			}
			break;
			case FO_POINT:
			case FO_NONE:
				switch(mMipFilter)
				{
					case FO_ANISOTROPIC:
					case FO_LINEAR:
						return GL_NEAREST_MIPMAP_LINEAR;
					case FO_POINT:
						return GL_NEAREST_MIPMAP_NEAREST;
					case FO_NONE:
						return GL_NEAREST;
				}
				break;
		}
		
		return 0;
	}
	
	void GLES2RenderSystem::_setTextureUnitFiltering(size_t unit, FilterType ftype, FilterOptions fo)
	{
		if (!activateGLTextureUnit(unit))
			return;
		
		switch (ftype)
		{
			case FT_MIN:
				if (mTextureMipmapCount == 0)
				{
					mMinFilter = FO_NONE;
				}
				else
				{
					mMinFilter = fo;
				}
				
				glTexParameteri(mTextureTypes[unit],
								GL_TEXTURE_MIN_FILTER,
								getCombinedMinMipFilter());
				break;
				
			case FT_MAG:
				switch (fo)
				{
					case FO_ANISOTROPIC:
					case FO_LINEAR:
						glTexParameteri(mTextureTypes[unit],
										GL_TEXTURE_MAG_FILTER,
										GL_LINEAR);
						break;
					case FO_POINT:
					case FO_NONE:
						glTexParameteri(mTextureTypes[unit],
										GL_TEXTURE_MAG_FILTER,
										GL_NEAREST);
						break;
				}
				break;
			case FT_MIP:
				if (mTextureMipmapCount == 0)
				{
					mMipFilter = FO_NONE;
				}
				else
				{
					mMipFilter = fo;
				}
				
				glTexParameteri(mTextureTypes[unit],
								GL_TEXTURE_MIN_FILTER,
								getCombinedMinMipFilter());
				break;
		}
		
		activateGLTextureUnit(0);
	}
	
	GLfloat GLES2RenderSystem::_getCurrentAnisotropy(size_t unit)
	{
		GLfloat curAniso = 0;
		glGetTexParameterfv(mTextureTypes[unit],
							GL_TEXTURE_MAX_ANISOTROPY_EXT, &curAniso);
		return curAniso ? curAniso : 1;
	}
	
	void GLES2RenderSystem::_setTextureLayerAnisotropy(size_t unit, unsigned int maxAnisotropy)
	{
		if (!mCurrentCapabilities->hasCapability(RSC_ANISOTROPY))
			return;
		
		if (!activateGLTextureUnit(unit))
			return;
		
		GLfloat largest_supported_anisotropy = 0;
		glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &largest_supported_anisotropy);
		if (maxAnisotropy > largest_supported_anisotropy)
			maxAnisotropy = largest_supported_anisotropy ?
			static_cast<uint>(largest_supported_anisotropy) : 1;
		if (_getCurrentAnisotropy(unit) != maxAnisotropy)
			glTexParameteri(mTextureTypes[unit], GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy);
		activateGLTextureUnit(0);
	}
	
	void GLES2RenderSystem::_render(const RenderOperation& op)
	{
		RenderSystem::_render(op);
		
		void* pBufferData = 0;
		bool multitexturing = (getCapabilities()->getNumTextureUnits() > 1);
		
		const VertexDeclaration::VertexElementList& decl = 
		op.vertexData->vertexDeclaration->getElements();
		VertexDeclaration::VetexElementList::const_iterator elem, elemEnd;
		elemEnd = decl.end();
		std::vector<GLuint> attribsBound;
		
		for (elem = decl.begin(); elem != elemEnd; ++elem)
		{
			if (!op->vertexData->vertexBufferBinding->isBufferBound(elem->getSource()))
				continue;
			HardwareVertexBufferSharedPtr vertexBuffer = 
			op.vertexData->vertexBufferBinding->getBuffer(elem->getSource());
			_bindGLBuffer(GL_ARRAY_BUFFER,
						  static_cast<const GLES2HardwareVertexBuffer*>(vertexBuffer.get())->getGLBufferId());
			pBufferData = VBO_BUFFER_OFFSET(elem->getOffset());
			
			if (op.vertexData->vertexStart)
			{
				pBufferData = static_cast<char*>pBufferData + op.vertexData->vertexStart * vertexBuffer->getVertexSize();
			}
			
			VertexElementSemantic sem = elem->getSemantic();
			
			unsigned short typeCount = VertexElement::getTypeCount(elem->getType());
			GLboolean normalised = GL_FALSE;
			
			GLSLESLinkProgram* linkProgram = GLSLESLinkProgramManager::getSingleton().getActiveLinkProgram();
			if (!linkProgram->isAttributeValid(sem, elem->getIndex()))
			{
				continue;
			}
			
			GLint attrib = linkProgram->getAttributeIndex(sem, elem->getIndex());
			
			switch (elem->getType())
			{
				case VET_COLOUR:
				case VET_COLOUR_ABGR:
				case VET_COLOUR_ARGB:
					typeCount = 4;
					normalised = GL_TRUE;
					break;
				default:
					break;
			};
			
			glVertexAttribPointer(attrib,
								  typeCount,
								  GLES2HardwareBufferManager::getGLType(elem->getType()),
								  normalised,
								  static_cast<GLsizei>(vertexBuffer->getVertexSize()),
								  pBufferData);
			glEnableVertexAttribArray(attrib);
			
			attribsBound.push_back(attrib);
		}
		
		if (multitexturing)
			activateGLTextureUnit(GL_TEXTUR0);
		
		GLint primType;
		switch (op.operationType)
		{
			case RenderOperation::OT_POINT_LIST:
				primType = GL_POINTS;
				break;
			case RenderOperation::OT_LINE_LIST:
				primType = GL_LINES;
				break;
			case RenderOperation::OT_LINE_STRIP:
				primType = GL_LINE_STRIP;
				break;
			default:
			case RenderOperation::OT_TRIANGLE_LIST:
				primType = GL_TRIANGLES;
				break;
			case RenderOperation::OT_TRIANGLE_STRIP:
				primType = GL_TRIANGLE_STRIP;
				break;
			case RenderOperation::OT_TRIANGLE_FAN:
				primType = GL_TRIANGLE_FAN;
				break;
		}
		
		if (op.useIndexes)
		{
			_bindGLBuffer(GL_ELEMENT_ARRAY_BUFFER,
						  static_cast<GLES2HardwareIndexBuffer*>(op.indexData->indexBuffer.get())->getGLBufferId());
			
			pBufferData = VBO_BUFFER_OFFSET(op.indexData->indexStart *
											op.indexData->indexBuffer->getIndexSize());
			
			GLenum indexType = (op.indexData->indexBuffer->getType() == HardwareIndexBuffer::IT_16BIT) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
			
			do
			{
				if (mDerivedDepthBias && mCurrentPassIterationNum > 0)
				{
					_setDepthBias(mDerivedDepthBiasBase +
								  mDerivedDepthBiasMultiplier * mCurrentPassIterationNum,
								  mDerivedDepthBiasSlopeScale);
				}
				glDrawElements((_getPolygonMode() == GL_FILL) ? primType : _getPolygonMode(), op.indexData->indexCount, indexType, pBufferData);
			} while (updatePassIterationRenderState());
		}
		else
		{
			do
			{
				if (mDerivedDepthBias && mCurrentPassIterationNum > 0)
				{
					_setDepthBias(mDerivedDepthBiasBase + 
								  mDerivedDepthBiasMultiplier * mCurrentPassIterationNum,
								  mDerivedDepthBiasSlopeScale);
				}
				glDrawArrays((_getPolygonMode() == GL_FILL) ? primType : _getPolygonMode(), 0, op.vertexData->vertexCount);
			} while (updatePassIterationRenderState());
		}
		
		for (std::vector<GLuint>::iterator ai = attribsBound.begin(); ai != attribsBound.end(); ++ai)
		{
			glDisableVertexAttribArray(*ai);
		}
	}
	
	void GLES2RenderSystem::setScissorTest(bool enabled, size_t left,
										   size_t top, size_t right,
										   size_t bottom)
	{
		bool flipping = mActiveRenderTarget->requiresTextureFlipping();
		size_t targetHeight = mActiveRenderTarget->getHeight();
		GLsizei w, h, x, y;
		
		if (enabled)
		{
			glEnable(GL_SCISSOR_TEST);
			x = left;
			if (flipping)
				y = top;
			else
				y = targetHeight - bottom;
			w = right - left;
			h = bottom - top;
			glScissor(x, y, w, h);
		}
		else
		{
			glDisable(GL_SCISSOR_TEST);
			
			w = mActiveRenderTarget->getActualWidth();
			h = mActiveRenderTarget->getActualHeight();
			x = mActiveRenderTarget->getActualLeft();
			if (flipping)
				y = mActiveRenderTarget->getActualTop();
			else 
				y = targetHeight - mActiveViewport->getActualTop() - h;
			glScissor(x, y, w, h);
		}
	}
	
	void GLES2RenderSystem::clearFrameBuffer(unsigned int buffers,
											 const ColourValue& colour,
											 Real depth, unsigned short stencil)
	{
		bool colourMask = !mColourWrite[0] || !mColourWrite[1] ||
						!mColourWrite[2] || !mColourWrite[3];
		
		GLbitfield flags = 0;
		if (buffers & FBT_COLOUR)
		{
			flags |= GL_COLOR_BUFFER_BIT;
			if (colourMask)
			{
				glColorMask(true, true, true, true);
			}
			glClearColor(colour.r, colour.g, colour.b, colour.a);
		}
		if (buffers & FBT_DEPTH)
		{
			flags |= GL_DEPTH_BUFFER_BIT;
			if (!mDepthWrite)
			{
				glDepthMask(GL_TRUE);
			}
			glClearDepthf(depth);
		}
		if (buffers & FBT_STENCIL)
		{
			flags |= GL_STENCIL_BUFFER_BIT;
			glStencilMask(0xFFFFFFFF);
			glClearStencil(stencil);
		}
		
		GLboolean scissorTestEnabled = glIsEnabled(GL_SCISSOR_TEST);
		if (!scissorTestEnabled)
		{
			glEnable(GL_SCISSOR_TEST);
		}
		
		GLint viewport[4], scissor[4];
		glGetIntegerv(GL_VIEWPORT, viewport);
		glGetIntegerv(GL_SCISSOR_BOX, scissor);
		bool scissorBoxDifference = 
			viewport[0] != scissor[0] || viewport[1] != scissor[1] ||
		viewport[2] != scissor[2] || viewport[3] != scissor[3];
		if (scissorBoxDifference)
		{
			glScissor(viewport[0], viewport[1], viewport[2], viewport[3]);
		}
		
		if (!scissorTestEnabled)
		{
			glDisable(GL_SCISSOR_TEST);
		}
		
		if (!mDepthWrite && (buffers & FBT_DEPTH))
		{
			glDepthMask(GL_FALSE);
		}
		
		if (colourMask && (buffers & FBT_COLOUR))
		{
			glColorMask(mColourWrite[0], mColourWrite[1], mColourWrite[2], mColourWrite[3]);
		}
		
		if (buffers & FBT_STENCIL)
		{
			glStencilMask(mStencilMask);
		}
	}
	
	void GLES2RenderSystem::_switchContext(GLES2Context* context)
	{
		if (mCurrentVertexProgram)
			mCurrentVertexProgram->unbindProgram();
		if (mCurrentFragmentProgram)
			mCurrentFragmentProgram->unbindProgram();
		
		_disableTextureUnitsFrom(0);
		
		if (mCurrentContext)
			mCurrentContext->endCurrent();
		mCurrentContext = context;
		mCurrentContext->setCurrent();
		
		if (!mCurrentContext->getInitialized())
		{
			_oneTimeContextInitialization();
			mCurrentContext->setInitialized();
		}
		
		if (mCurrentVertexProgram)
			mCurrentVertexProgram->bindProgram();
		if (mCurrentFragmentProgram)
			mCurrentFragmentProgram->bindProgram();
		
		glDepthMask(mDepthWrite);
		glColorMask(mColourWrite[0], mColourWrite[1], mColourWrite[2], mColourWrite[3]);
		glStencilMask(mStencilMask);
	}
	
	void GLES2RenderSystem::_unregisterContext(GLES2Context* context)
	{
		if (mCurrentContext == context)
		{
			if (mCurrentContext != mMainContext)
			{
				_switchContext(mMainContext);
			}
			else
			{
				mCurrentContext->endCurrent();
				mCurrentContext = 0;
				mMainContext = 0;
			}
		}
	}
	
	void GLES2RenderSystem::_oneTimeContextInitialization()
	{
		glDisable(GL_DITHER);
	}
	
	void GLES2RenderSystem::initialiseContext()
	{
		mGLSupport->initialiseExtensions();
		LogManager::getSingleton().logMessage("**************************************");
		LogManager::getSingleton().logMessage("*** OpenGL ES 2.x Renderer Started ***");
		LogManager::getSingleton().logMessage("**************************************");
	}
	
	void GLES2RenderSystem::_setRenderTarget(RenderTarget* target)
	{
		if (mActiveRenderTarget)
			mRTTManager->unbind(mActiveRenderTarget);
		
		mActiveRenderTarget = target;
		if (target)
		{
			GLES2Context* newContext = 0;
			target->getCustomAttribute("GLCONTEXT", &newContext);
			if (newContext && mCurrentContext != newContext)
			{
				_switchContext(newContext);
			}
			
			GLES2DepthBuffer* depthBuffer = static_cast<GLES2DepthBuffer*>(target->getDepthBuffer());
			
			if (target->getDepthBufferPool() != DepthBuffer::POOL_NO_DEPTH &&
				(!depthBuffer || depthBuffer->getGLContext() != mCurrentContext))
			{
				setDepthBufferFor(target);
			}
			
			mRTTManager->bind(target);
		}
	}
	
	GLint GLES2RenderSystem::convertCompareFunction(CompareFunction func) const
	{
		switch (func)
		{
			case CMPF_ALWAYS_FAIL:
				return GL_NEVER;
			case CMPF_ALWAYS_PASS:
				return GL_ALWAYS;
			case CMPF_LESS:
				return GL_LESS;
			case CMPF_LESS_EQUAL:
				return GL_LEQUAL;
			case CMPF_EQUAL:
				return GL_EQUAL;
			case CMPF_NOT_EQUAL:
				return GL_NOTEQUAL;
			case CMPF_GREATER_EQUAL:
				return GL_GEQUAL;
			case CMPF_GREATER:
				return GL_GREATER;
		};
		
		return GL_ALWAYS;
	}
	
	GLint GLES2RenderSystem::convertStencilOp(StencilOperation op, bool invert) const
	{
		switch (op)
		{
			case SOP_KEEP:
				return GL_KEEP;
			case SOP_ZERO:
				return GL_ZERO;
			case SOP_REPLACE:
				return GL_REPLACE;
			case SOP_INCREMENT:
				return invert ? GL_DECR : GL_INCR;
			case SOP_DECREMENT:
				return invert ? GL_INCR : GL_DECR;
			case SOP_INCREMENT_WRAP:
				return invert ? GL_DECR_WRAP : GL_INCR_WRAP;
			case SOP_DECREMENT_WRAP:
				return invert ? GL_INCR_WRAP : GL_DECR_WRAP;
			case SOP_INVERT:
				return GL_INVERT;
		};
		
		return SOP_KEEP;
	}
	
	void GLES2RenderSystem::bindGpuProgram(GpuProgram* prg)
	{
		GLES2GpuProgram* glprg = static_cast<GLES2GpuProgram*>(prg);
		
		switch (glprg->getType())
		{
			case GPT_VERTEX_PROGRAM:
				if (mCurrentVertexProgram != glprg)
				{
					if (mCurrentVertexProgram)
						mCurrentVertexProgram->unbindProgram();
					mCurrentVertexProgram = glprg;
				}
				break;
			case GPT_FRAGMENT_PROGRAM:
				if (mCurrentFragmentProgram != glprg)
				{
					if (mCurrentFragmentProgram)
						mCurrentFragmentProgram->unbindProgram();
					mCurrentFragmentProgram = glprg;
				}
				break;
			case GPT_GEOMETRY_PROGRAM:
			default:
				break;
		}
		
		glprg->bindProgram();
		
		RenderSystem::bindGpuProgram(prg);
	}
	
	void GLES2RenderSystem::unbindGpuProgram(GpuProgramType gptype)
	{
		if (gptype == GPT_VERTEX_PROGRAM && mCurrentVertexProgram)
		{
			mActiveVertexGpuProgramParameters.setNull();
			mCurrentVertexProgram->unbindProgram();
			mCurrentVertexProgram = 0;
		}
		else if (gptype == GPT_FRAGMENT_PROGRAM && mCurrentFragmentProgram)
		{
			mActiveFragmentGpuProgramParameters.setNull();
			mCurrentFragmentProgram->unbindProgram();
			mCurrentFragmentProgram = 0;
		}
		RenderSystem::unbindGpuProgram(gptype);
	}
	
	void GLES2RenderSystem::bindGpuProgramParameters(GpuProgramType gptype, GpuProgramParametersSharedPtr params, uint16 mask)
	{
		if (mask & (uint16)GPV_GLOBAL)
		{
			params->_copySharedParams();
		}
		switch (gptype)
		{
			case GPT_VERTEX_PROGRAM:
				mActiveVertexGpuProgramParameters = params;
				mCurrentVertexProgram->bindProgramParameters(params, mask);
				break;
			case GPT_FRAGMENT_PROGRAM:
				mActiveFragmentGpuProgramParameters = params;
				mCurrentFragmentProgram->bindProgramParameters(params, mask);
				break;
			case GPT_GEOMETRY_PROGRAM:
			default:
				break;
		}
	}
	
	void GLES2RenderSystem::bindGpuProgramPassIterationParameters(GpuProgramType gptype)
	{
		switch (gptype)
		{
			case GPT_VERTEX_PROGRAM:
				mCurrentVertexProgram->bindProgramPassIterationParameters(mActiveVertexGpuProgramParameters);
				break;
			case GPT_FRAGMENT_PROGRAM:
				mCurrentFragmentProgram->bindProgramPassIterationParameters(mActiveFragmentGpuProgramParameters);
				break;
			case GPT_GEOMETRY_PROGRAM:
			default:
				break;
		}
	}
	
	unsigned int GLES2RenderSystem::getDisplayMonitorCount() const
	{
		return 1;
	}
	
	bool GLES2RenderSystem::activateGLTextureUnit(size_t unit)
	{
		if (mActiveTextureUnit != unit)
		{
			if (unit < getCapabilities()->getNumTextureUnits())
			{
				glActiveTexture(GL_TEXTURE0 + unit);
				mActiveTextureUnit = unit;
				return true;
			}
			else if (!unit)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		else
		{
			return true;
		}
	}
	
	void GLES2RenderSystem::_bindGLBuffer(GLenum target, GLuint buffer)
	{
		BindBufferMap::iterator i = mActiveBufferMap.find(target);
		if (i == mActiveBufferMap.end())
		{
			mActiveBufferMap.insert(BindBufferMap::value_type(target, buffer));
			
			glBindBuffer(target, buffer);
		}
		else
		{
			if ((*i).second != buffer)
			{
				(*i).second = buffer;
				glBindBuffer(target, buffer);
			}
		}
	}
}
